<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.4: http://docutils.sourceforge.net/" />
<title>i18n的实现</title>
<style type="text/css">

/*
:Author: David Goodger
:Contact: goodger@users.sourceforge.net
:Date: $Date: 2005-12-18 01:56:14 +0100 (Sun, 18 Dec 2005) $
:Revision: $Revision: 4224 $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.

See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/

/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
  border: 0 }

table.borderless td, table.borderless th {
  /* Override padding for "table.docutils td" with "! important".
     The right padding separates the table cells. */
  padding: 0 0.5em 0 0 ! important }

.first {
  /* Override more specific margin styles with "! important". */
  margin-top: 0 ! important }

.last, .with-subtitle {
  margin-bottom: 0 ! important }

.hidden {
  display: none }

a.toc-backref {
  text-decoration: none ;
  color: black }

blockquote.epigraph {
  margin: 2em 5em ; }

dl.docutils dd {
  margin-bottom: 0.5em }

/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
  font-weight: bold }
*/

div.abstract {
  margin: 2em 5em }

div.abstract p.topic-title {
  font-weight: bold ;
  text-align: center }

div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
  margin: 2em ;
  border: medium outset ;
  padding: 1em }

div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
  font-weight: bold ;
  font-family: sans-serif }

div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
  color: red ;
  font-weight: bold ;
  font-family: sans-serif }

/* Uncomment (and remove this text!) to get reduced vertical space in
   compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
  margin-bottom: 0.5em }

div.compound .compound-last, div.compound .compound-middle {
  margin-top: 0.5em }
*/

div.dedication {
  margin: 2em 5em ;
  text-align: center ;
  font-style: italic }

div.dedication p.topic-title {
  font-weight: bold ;
  font-style: normal }

div.figure {
  margin-left: 2em ;
  margin-right: 2em }

div.footer, div.header {
  clear: both;
  font-size: smaller }

div.line-block {
  display: block ;
  margin-top: 1em ;
  margin-bottom: 1em }

div.line-block div.line-block {
  margin-top: 0 ;
  margin-bottom: 0 ;
  margin-left: 1.5em }

div.sidebar {
  margin-left: 1em ;
  border: medium outset ;
  padding: 1em ;
  background-color: #ffffee ;
  width: 40% ;
  float: right ;
  clear: right }

div.sidebar p.rubric {
  font-family: sans-serif ;
  font-size: medium }

div.system-messages {
  margin: 5em }

div.system-messages h1 {
  color: red }

div.system-message {
  border: medium outset ;
  padding: 1em }

div.system-message p.system-message-title {
  color: red ;
  font-weight: bold }

div.topic {
  margin: 2em }

h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
  margin-top: 0.4em }

h1.title {
  text-align: center }

h2.subtitle {
  text-align: center }

hr.docutils {
  width: 75% }

img.align-left {
  clear: left }

img.align-right {
  clear: right }

ol.simple, ul.simple {
  margin-bottom: 1em }

ol.arabic {
  list-style: decimal }

ol.loweralpha {
  list-style: lower-alpha }

ol.upperalpha {
  list-style: upper-alpha }

ol.lowerroman {
  list-style: lower-roman }

ol.upperroman {
  list-style: upper-roman }

p.attribution {
  text-align: right ;
  margin-left: 50% }

p.caption {
  font-style: italic }

p.credits {
  font-style: italic ;
  font-size: smaller }

p.label {
  white-space: nowrap }

p.rubric {
  font-weight: bold ;
  font-size: larger ;
  color: maroon ;
  text-align: center }

p.sidebar-title {
  font-family: sans-serif ;
  font-weight: bold ;
  font-size: larger }

p.sidebar-subtitle {
  font-family: sans-serif ;
  font-weight: bold }

p.topic-title {
  font-weight: bold }

pre.address {
  margin-bottom: 0 ;
  margin-top: 0 ;
  font-family: serif ;
  font-size: 100% }

pre.literal-block, pre.doctest-block {
  margin-left: 2em ;
  margin-right: 2em ;
  background-color: #eeeeee }

span.classifier {
  font-family: sans-serif ;
  font-style: oblique }

span.classifier-delimiter {
  font-family: sans-serif ;
  font-weight: bold }

span.interpreted {
  font-family: sans-serif }

span.option {
  white-space: nowrap }

span.pre {
  white-space: pre }

span.problematic {
  color: red }

span.section-subtitle {
  /* font-size relative to parent (h1..h6 element) */
  font-size: 80% }

table.citation {
  border-left: solid 1px gray;
  margin-left: 1px }

table.docinfo {
  margin: 2em 4em }

table.docutils {
  margin-top: 0.5em ;
  margin-bottom: 0.5em }

table.footnote {
  border-left: solid 1px black;
  margin-left: 1px }

table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
  padding-left: 0.5em ;
  padding-right: 0.5em ;
  vertical-align: top }

table.docutils th.field-name, table.docinfo th.docinfo-name {
  font-weight: bold ;
  text-align: left ;
  white-space: nowrap ;
  padding-left: 0 }

h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
  font-size: 100% }

tt.docutils {
  background-color: #eeeeee }

ul.auto-toc {
  list-style-type: none }

</style>
</head>
<body>
<div class="document" id="i18n">
<h1 class="title">i18n的实现</h1>
<table class="docinfo" frame="void" rules="none">
<col class="docinfo-name" />
<col class="docinfo-content" />
<tbody valign="top">
<tr class="field"><th class="docinfo-name">作者:</th><td class="field-body">limodou</td>
</tr>
<tr class="field"><th class="docinfo-name">联系:</th><td class="field-body"><a class="reference" href="mailto:limodou&#64;gmail.com">limodou&#64;gmail.com</a></td>
</tr>
<tr class="field"><th class="docinfo-name">主页:</th><td class="field-body"><a class="reference" href="http://code.google.com/p/ulipad/">http://code.google.com/p/ulipad/</a></td>
</tr>
<tr class="field"><th class="docinfo-name">BLOG:</th><td class="field-body"><a class="reference" href="http://www.donews.net/limodou">http://www.donews.net/limodou</a></td>
</tr>
<tr class="field"><th class="docinfo-name">版权:</th><td class="field-body">GPL</td>
</tr>
</tbody>
</table>
<div class="contents topic">
<p class="topic-title first"><a id="contents" name="contents">Contents</a></p>
<ul class="simple">
<li><a class="reference" href="#id1" id="id6" name="id6">一、通常的实现方法</a></li>
<li><a class="reference" href="#id2" id="id7" name="id7">二、遇到的问题</a></li>
<li><a class="reference" href="#id3" id="id8" name="id8">三、解决方法</a></li>
<li><a class="reference" href="#ulipad" id="id9" name="id9">四、UliPad中的具体使用</a></li>
<li><a class="reference" href="#gettext" id="id10" name="id10">附：gettext的实现原理</a></li>
<li><a class="reference" href="#id4" id="id11" name="id11">i18n的一个小问题</a></li>
</ul>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id6" id="id1" name="id1">一、通常的实现方法</a></h1>
<p>什么是i18n，它是国际化的简称(Internationalization，去掉开始的I和最后的N，中间一共18个字符)。对于它的实现，
在 Python 的文档关于 gettext 中有详细的描述。同时我在wxPyWiki上找到关于wxPyCookbook上关于国际化实现的教程，
我便按文档和教程开始了我的实验。</p>
<p>例子我使用教程上提供的，不过我进行了修改。一是增加了中文，去掉了西班牙语和法语的处理。二是将某些信息放在另一
个模块中，测试跨模块的实现。同时在这个模块中将进行国际化处理的内容放在了列表中，如:</p>
<pre class="literal-block">
message = [_('English'), _('Chinese')]
</pre>
<p>下面我描述一个通常的方法。</p>
<ol class="arabic">
<li><p class="first">在主模块中导入gettext模块，它需要放在进行国际化处理的语句、模块导入之前。如:</p>
<pre class="literal-block">
gettext.install('i18ntest', './locale', unicode=True)
</pre>
<p>三个参数的意思分别为：</p>
<ul class="simple">
<li>作用域名，用于限定翻译文件的主名</li>
<li>路径，存放翻译文件的路径</li>
<li>unicode，是否使用unicode(如果你的应用程序是unicode的，则此处应为True)</li>
</ul>
<p>上述的指令将安装一个缺省的国际化处理类。在我们需要安装某种特定的国际化处理类时，我们可以:</p>
<pre class="literal-block">
gettext.translation('i18ntest', './locale', languages=['en']).install(True)
</pre>
<p>这样将安装指定的翻译文件。前两个参数同gettext.install，第三个参数指明语言的种类。gettext.translation将
返回一个新的对象。执行它的install函数将安装支持指定语言的国际化处理功能。install中的参为是否使用unicode。</p>
</li>
<li><p class="first">在所有需要进行国际化处理的字符串上加上_()，如：English和Chinese需要国际化处理，那么要转化为：_('English')
和_('Chinese')。这一步工作可能会比较麻烦。</p>
<p>经过上述的处理，你的程序虽然还没有真正的翻译文件但仍然是可以运行的。</p>
</li>
<li><p class="first">使用pygettext.py进行字符串的抽取。pygettext.py是在python的安装包中自带的一个工具，它位于tools/i18n目录
中，同时还有一个叫msgfmt.py的程序，是用来将翻译文件转换成gettext可识别的二进制文件。命令行参数如下:</p>
<pre class="literal-block">
Python pygettext.py 文件名
</pre>
<p>文件名可以同时有多个。执行python pygettext.py --help 可以看它支持哪些选项。如果没有指定输出文件名，则缺
省会生成一个名为messages.pot的文件。它就是最原始的用来翻译成其它语言的文件。打开它你会看到可能如下的内容:</p>
<pre class="literal-block">
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR &lt;EMAIL&#64;ADDRESS&gt;, YEAR.
#
msgid &quot;&quot;
msgstr &quot;&quot;
&quot;Project-Id-Version: PACKAGE VERSION\n&quot;
&quot;POT-Creation-Date: Mon Jun 14 13:28:26 2004\n&quot;
&quot;PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n&quot;
&quot;Last-Translator: FULL NAME &lt;\n&quot;&gt;EMAIL&#64;ADDRESS&gt;\n&quot;
&quot;Language-Team: LANGUAGE &lt;\n&quot;&gt;LL&#64;li.org&gt;\n&quot;
&quot;MIME-Version: 1.0\n&quot;
&quot;Content-Type: text/plain; charset=CHARSET\n&quot;
&quot;Content-Transfer-Encoding: ENCODING\n&quot;
&quot;Generated-By: pygettext.py 1.5\n&quot;

#: i18ntest.py:28 i18ntest.py:76
msgid &quot;MiniApp&quot;
msgstr &quot;&quot;

#: i18ntest.py:39 i18ntest.py:85
msgid &quot;E&amp;xit&quot;
msgstr &quot;&quot;
</pre>
<p>一个messages.pot由以下内容组成:</p>
<ul>
<li><p class="first">'#'开始的注释行</p>
</li>
<li><p class="first">空行</p>
</li>
<li><p class="first">msgid行，表明一个翻译项，可以有多行。如果为多行，则形如:</p>
<pre class="literal-block">
msgid &quot;&quot;
&quot;This is a multiline\n&quot;
&quot;test&quot;
如果msgid内容为空，则表示msgstr是文档的信息
</pre>
</li>
<li><p class="first">msgstr行，表明一个译文，可以为多行，格式同msgid。如果msgid为空，则为文档信息。在文档信息中可以录入关
于此译文的一些情况，同时最重要的就是charset域，用于指明此文档的编码信息，也就是保存此文档时所用的编码。
建议对中文使用utf-8编码。</p>
</li>
</ul>
<p>那么我们得到这个pot文件后，先将其保存为不同的版本，以便下面的翻译。如：messages_cn.po。翻译时，只考虑
msgid不为空的地方，将译文写在msgstr处即可。</p>
</li>
<li><p class="first">使用msgfmt.py将po文件转换为二进制的mo文件。命令行如下:</p>
<pre class="literal-block">
python msgfmt.py messages_cn.po
</pre>
<p>这样将生成一个名为messages_cn.mo的二进制文件。</p>
</li>
<li><p class="first">安装mo文件。其实就是将mo文件放在正确的位置。gettext要求翻译文件组织成如下目录:</p>
<pre class="literal-block">
./locale/en/LC_MESSAGES
./locale/cn/LC_MESSAGES
</pre>
<p>locale是在gettext.translation中指定的。en, cn是语言分类。LC_MESSAGES是要求的。同时mo文件应改为与作用域
名相同，如i18ntest.mo。</p>
<p>经过以上的步骤，一个支持国际化处理的程序可用了。</p>
<p>在wxPyWiki教程中使用了mki18n.py的程序。但我使用了python自带的工具，与教程有所区别。</p>
</li>
</ol>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id7" id="id2" name="id2">二、遇到的问题</a></h1>
<p>采用通常的方法存在一些不方便的地方，感觉有以下几点：</p>
<ol class="arabic simple">
<li>pygettext.py可以一次读取多个文件，但当文件太多时，在命令行下不容易放下。不支持目录和文件列表的处理。</li>
<li>当我的程序修改后，需要翻译的地方也发生了变化，如果将新的变化与原来已经翻译好的内容合并？</li>
<li>local/en/LC_MESSAGE这种目录组织有些麻烦。</li>
</ol>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id8" id="id3" name="id3">三、解决方法</a></h1>
<p>对于第一个问题，我的解决方法是修改pygettext.py源程序。我没有实现目录的处理，实现的是文件列表的处理。首先将
需要进行处理的 Python 源文件名生成一个文件，一个文件占一行。然后在pygettext.py中增加处理文件列表的命令行选
项。修改后的文件如果有感兴趣的可以下载最新的 UliPad.py 源代码(在本文发表时，只可以从cvs下载)。</p>
<p>对于第二个问题，我的解决方法是编写一个合并程序。它首先将已经翻译好的文件读取出来，按关键字保存到一个字典中。
再读取最新生成的翻译文件，对翻译文件中的每一个翻译项，如果字典中不存在，则直接将此项插入到字典中；如果存在，
并且译文不为空的话，则替换原译文。处理完新的翻译文件后，再去除字典中的关键字在新的翻译文件中不存在的项。这一
点可能要注意，因为我每次生成的新的翻译文件都是一个全集，因此我采用这种方法是为了去除无用的翻译项。如果新的翻
译文件不是全集，应该把去除字典关键字的操作去掉。但如果要去除无用的翻译项只能由人工来完成了。此程序也在 UliPad 源代码中。</p>
<p>对于第三个问题，我查阅了gettext.py源代码。原因就出在find()函数。它最后需要生成一个mo文件名，代码如下:</p>
<pre class="literal-block">
mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain)
</pre>
<p>可以看到一个mo文件是由目录名、语言的种类、LC_MESSAGES、和作用域名组成的。形如:</p>
<pre class="literal-block">
localedir/lang/LC_MESSAGES/domain.mo
</pre>
<p>因此，需要象这样建立相应的目录，并将mo文件放到相应的目录下。</p>
<p>我的想法是：将所有翻译文件放在同一目录（如果项目很大，涉及到许多程序的翻译就另当别论），
翻译文件为：主名_地区编码.mo。为什么不用语言编码呢？因为象中文有简体中文和繁体中文的区别，语言编码都是zh，
这样根本区分不出来。如果用地区编码则可以，分别为cn和tw。</p>
<p>因此为了实现此功能，我根据gettext编写了自已的i18n模式，在实现了gettext功能的基础上还满足了我的要求。同时此
模块还可以根据操作缺省的locale自动调用相应的翻译文件。此文件同在 UliPad 源代码中。</p>
<p>这样，在 UliPad 中与i18n有关的内容有：</p>
<ul class="simple">
<li>pygettext.py(修改)</li>
<li>msgfmt.py</li>
<li>i18n.py(实现gettext功能)</li>
<li>mergepo.py(合并po语言文件)</li>
</ul>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id9" id="ulipad" name="ulipad">四、UliPad中的具体使用</a></h1>
<p>在UliPad的启动程序--UliPad.py中靠近前面的地方加入:</p>
<pre class="literal-block">
1    sys.path.append('./modules')
2    import i18n
3    i18n = i18n.I18n('UliPad', './lang', unicode=True)
4    try:
5        import Lang
6    except:
7        pass
8    else:
9        i18n.install(Lang.language)
</pre>
<p>i18n是放在modules目录中的。第三行为生成一个i18n对象，它的调用参数同gettext.install。这样运行到第3行，将会
自动调用缺省翻译文件。缺省翻译文件为：取系统缺省locale，得到地区代码，判断与地区代码相匹配的翻译文件是否存
在，如果存在则装入；如果不存在，则不使用任何翻译文件，这样系统没有国际化的处理。</p>
<p>第5行是导入Lang模块。它其实是我使用了一个小花招。用户可以指定语言的种类，如果指定完毕，则会在modules目录下
生成一个Lang.py文件，其内容为：language=&quot;语言&quot;。也就是将用户选定的语言地区代码保存到这个文件中，以便下一次
启动时调用相应的语言文件。因此，UliPad 在用户修改了语言之后，只有重启新的语言设置才会生效。</p>
<p>导入i18n只需要在启动模块中导入即可，其它模块不用导入。</p>
<p>然后在所有可以翻译的地方加入_()(好累的工作)。</p>
<p>接着生成了一个文件清单。</p>
<p>调用pygettext.py生成语言文件UliPad_cn.po。</p>
<p>对UliPad_cn.po进行翻译。</p>
<p>调用msgfmt.py将UliPad_cn.po编译成mo文件。</p>
<p>将UliPad_cn.mo文件拷贝到lang目录下。</p>
<p>启动 UliPad ，OK，基本上没问题了。</p>
<p>如果对源程序有修改，则再调用pygettext.py生成新的翻译文件messages.pot。调用mergepo.py(合并语言文件)将新旧
进行融合。再生成mo文件，拷贝到lang目录下，如此循环。</p>
<p>但在 UliPad 中还存在一个问题：如何处理xml的资源文件。</p>
<p>在 UliPad 中，象查找、查找并替换、包括文本、反包括文本这几项功能的对话框都是使用了xml的资源文件，因此无法
象一般的源程序一样加入_()的处理。我的方法就是生成不同语言的xml资源文件，在需要使用资源文件时，根据程序选定
的语言，调用相应的资源文件。因为资源文件是xml文件，因此文本的编码应与xml的encoding声明相一致。对于中文的翻
译文件，建议使用utf-8编码。因为utf-8是xml缺省使用的编码。</p>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id10" id="gettext" name="gettext">附：gettext的实现原理</a></h1>
<p>只是本人的理解!</p>
<p>gettext的功能其实挺简单：定义_()函数并安装，根据传入的翻译项从mo文件中取得译文，并根据uniocde标识进行应用
的unicode编码处理。</p>
<p>一个gettext对象提供了两个基本的获得译文的函数：gettext和ugettext。一个用来获得未进行编码转换的译文，一个用
来获得转成unicode的译文。根据unicode的标志，这两个函数之一将被引用到_()函数上。如果未安装任何译文，则缺省返
回是只是翻译项，即原样返回。因此不安装任何译文，gettext一样是可以用的。</p>
<p>如何安装_()。</p>
<pre class="literal-block">
import __buildin__
__buildin__.__dict__['_']=unicode and self.ugettext or self.gettext
</pre>
<p>这样就将_()函数变成一个内置函数了，在其它的后续的模块中就可以直接使用它了。因此，使用gettext时，安装要很靠
前，并且只需要在启动模块中装入即可。</p>
<p>后记：为什么 UliPad 不动态更新界面</p>
<p>既然实现了国际化处理，如果用户改变了使用的语言，如果整个应用立即改变不是更好吗？当然是很吸引人，但对于 UliPad
存在问题。一方面所有界面与国际化处理相关的地方，都要用代码去实现更新过程，工作量大。另外，除非测试，一个用户
不会总是在各种语言之间切换来切换去，一般都是固定使用某种语言，因此，在重新启动后启用新的语言设置是可以接受的
方案。还有一个重要的原因就是：使用_()这种方式只适合函数调用方式，对于全局变量或模块变量无能为力。为什么呢？比如:</p>
<pre class="literal-block">
message = _('Chinese')
</pre>
<p>这样定义了一个变量。如果它是存在一个模块中，作为模块的全局变量的话，当导入这个模块时，这个语句会执行。并且导
入过程只会执行一次。这样当语句执行完毕后message就是具体的值，而不再是message=_('Chinese')这种语句了。这样，
当我们改变了语言，message只会是上一个语言的译文，不会变成新的译文。这一点真是很难解决。而为什么在函数调用时
可以呢？严格来说应该是这样的形式可以:</p>
<pre class="literal-block">
call(_('Chinese'))
</pre>
<p>这是因为，当调用函数时参数是动态计算的，因此每次调用call时都会重新计算参数的值，因此这种方式可以的。总的来
说，如果在执行时可以每次调用_()函数的话，这样就可以实现动态语言切换。如果不行，则使用静态语言切换。因此 UliPad
使用了静态语言的切换。</p>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id11" id="id4" name="id4">i18n的一个小问题</a></h1>
<p>不仅是我发现，别的使用 UliPad 的人也发现，有时在 Shell 窗口中执行过一些语句后，再回到文档窗口中时，随着鼠
标的移动文本自动会选中，甚至按键都不起作用。这个问题很有趣，为什么执行过Shell命令就不行了呢？后来我发现，这
是因为i18n中使用的_()函数与Shell中的_变量冲突所致。在Shell中，执行完一条命令后，命令的结果就保存在_变量中。
但i18n把这个'_'使用为一个内置的函数，因此造成两者的冲突。于是我不得不将 UliPad 中的所有_()函数全部改成了
tr()(自定义的)，这下子才解决这个问题。从而可以说明，PyShell(使用它生成的Shell窗口)的变量空间与UliPad是有
重叠的。可能也没有办法，因为i18n中的_()函数是通过将其置入内置__builtin__中的，而__builtin__是全局共享的，
因此对它有改变势必会影响到别的模块。</p>
<p>还好这个问题是可以解决的。但PyShell好象不支持中文。本来想多实现一些功能，但却带来一些负作用，小心为妙呀。</p>
<p><a class="reference" href="technical.htm">[返回]</a></p>
</div>
</div>
</body>
</html>
